package main

import (
	"archive/zip"
	"encoding/json"
	"flag"
	"crypto/sha1"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"encoding/hex"
	"github.com/schollz/progressbar/v3"
)

// AssetIndex represents the asset index
type AssetIndex struct {
	ID  string
	URL string
}

// Index represents the parsed index
type Index struct {
	Objects map[string]Object
}

// Object represents an object in the index
type Object struct {
	Hash string
}

// Downloadable interface
type Downloadable interface {
	Download(gameDir string) error
}

// Implement Downloadable for AssetIndex
func (ai *AssetIndex) Download(gameDir string) error {
	fmt.Printf("Downloading asset index: %s\n", ai.ID)

	// Create directories
	indexesDir := filepath.Join(gameDir, "assets", "indexes")
	if err := os.MkdirAll(indexesDir, os.ModePerm); err != nil {
		return err
	}

	configJSON := filepath.Join(indexesDir, fmt.Sprintf("%s.json", ai.ID))

	// Download the index file
	resp, err := http.Get(ai.URL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	file, err := os.Create(configJSON)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		return err
	}

	// Parse the index file
	index, err := ParseIndex(configJSON)
	if err != nil {
		return err
	}

	objectsDir := filepath.Join(gameDir, "assets", "objects")
	if err := os.MkdirAll(objectsDir, os.ModePerm); err != nil {
		return err
	}

	for _, obj := range index.Objects {
		hash := obj.Hash
		hashFirstTwo := hash[:2]

		firstTwoDir := filepath.Join(objectsDir, hashFirstTwo)
		if err := os.MkdirAll(firstTwoDir, os.ModePerm); err != nil {
			return err
		}

		hashedAssetFile := filepath.Join(firstTwoDir, hash)

		if _, err := os.Stat(hashedAssetFile); err == nil {
			// File exists, check hash
			if err := checkHash(hashedAssetFile, hash); err == nil {
				continue
			} else {
				if err := os.Remove(hashedAssetFile); err != nil {
					return err
				}
			}
		}

		// Download the asset
		url := fmt.Sprintf("https://resources.download.minecraft.net/%s/%s", hashFirstTwo, hash)
		fmt.Printf("Downloading asset: %s\n", hash)

		resp, err := http.Get(url)
		if err != nil {
			return err
		}
		defer resp.Body.Close()

		file, err := os.Create(hashedAssetFile)
		if err != nil {
			return err
		}
		defer file.Close()

		_, err = io.Copy(file, resp.Body)
		if err != nil {
			return err
		}
	}

	return nil
}

// ParseIndex parses the index file
func ParseIndex(filePath string) (*Index, error) {
	// Implement parsing logic here
	// For simplicity, assume it returns a dummy index
	return &Index{
		Objects: map[string]Object{
			"example": {Hash: "examplehash"},
		},
	}, nil
}

// checkHash checks the hash of a file
func checkHash(filePath, expectedHash string) error {
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

	hash := sha1.New()
	if _, err := io.Copy(hash, file); err != nil {
		return err
	}

	actualHash := hex.EncodeToString(hash.Sum(nil))
	if actualHash != expectedHash {
		return fmt.Errorf("hash mismatch: expected %s, got %s", expectedHash, actualHash)
	}

	return nil
}


// VersionManifest represents the Minecraft version manifest
type VersionManifest struct {
	Latest struct {
		Release string `json:"release"`
		Snapshot string `json:"snapshot"`
	} `json:"latest"`
	Versions []struct {
		ID string `json:"id"`
		Type string `json:"type"`
		URL string `json:"url"`
		Time string `json:"time"`
		ReleaseTime string `json:"releaseTime"`
	} `json:"versions"`
}

// Version represents a Minecraft version
type Version struct {
	AssetIndex struct {
		ID string `json:"id"`
	} `json:"assetIndex"`
	Libraries []Library `json:"libraries"` // 修改为明确的 Library 类型
	MainClass string `json:"mainClass"`
}

// VersionInfo represents a Minecraft version entry in the manifest
type VersionInfo struct {
	ID           string `json:"id"`
	Type         string `json:"type"`
	URL          string `json:"url"`
	Time         string `json:"time"`
	ReleaseTime  string `json:"releaseTime"`
}

// Library represents a Minecraft library
type Library struct {
	Name      string `json:"name"`
	Downloads struct {
		Artifact struct {
			Path string `json:"path"`
			SHA1 string `json:"sha1"`
			Size int32  `json:"size"`
			URL  string `json:"url"`
		} `json:"artifact"`
	} `json:"downloads"`
}

func main() {
	action := flag.String("action", "", "Action to perform: search, download, boot")
	version := flag.String("version", "", "Minecraft version to search/download/boot")
	versionType := flag.String("type", "release", "Type of Minecraft version: release or snapshot")

	flag.Parse()

	switch *action {
	case "search":
		searchVersion(*version, *versionType)
	case "download":
		if *version == "" {
			fmt.Println("Error: Version is required for download action")
			return
		}
		downloadVersion(*version)
	case "boot":
		if *version == "" {
			fmt.Println("Error: Version is required for boot action")
			return
		}
		bootVersion(*version)
	default:
		fmt.Println("Error: Invalid action. Use 'search', 'download', or 'boot'.")
	}
}

func searchVersion(version, versionType string) {
	manifest := getManifest()
	for _, v := range manifest.Versions {
		if (version == "" || strings.Contains(v.ID, version)) && v.Type == versionType {
			fmt.Printf("Remote Support Version: %s\n", v.ID)
		}
	}
}

func downloadVersion(version string) {
	manifest := getManifest()

	for _, v := range manifest.Versions {
		if v.ID == version {
			if v.URL == "" {
				fmt.Printf("Error: URL for version %s is missing or invalid\n", version)
				return
			}

			gameDir := filepath.Join(".", ".minecraft")
			err := downloadVersionFiles(v, gameDir)
			if err != nil {
				fmt.Printf("Download Error: %v\n", err)
				return
			}
			fmt.Printf("Downloaded version: %s\n", version)
			return
		}
	}
	fmt.Printf("Version: %s not found\n", version)
}


func bootVersion(version string) {
	gameDir := filepath.Join(".", ".minecraft")
	versionsDir := filepath.Join(gameDir, "versions")
	versionDir := filepath.Join(versionsDir, version)
	configPath := filepath.Join(versionDir, fmt.Sprintf("%s.json", version))
	versionPath := filepath.Join(versionDir, fmt.Sprintf("%s.jar", version))

	// 检查版本文件夹是否存在
	if _, err := os.Stat(versionDir); os.IsNotExist(err) {
		fmt.Printf("Version directory does not exist: %s\n", versionDir)
		fmt.Printf("Please download the version first using the 'download' action.\n")
		return
	}

	// 检查 JAR 文件是否存在
	if _, err := os.Stat(versionPath); os.IsNotExist(err) {
		fmt.Printf("Version JAR file does not exist: %s\n", versionPath)
		fmt.Printf("Please download the version first using the 'download' action.\n")
		return
	}

	// 加载版本配置
	versionData := loadVersionConfig(configPath)

	// 创建 natives 文件夹
	nativesDir := filepath.Join(versionDir, "natives")
	if _, err := os.Stat(nativesDir); os.IsNotExist(err) {
		os.MkdirAll(nativesDir, 0755)
	}

	// 提取 natives 文件
	for _, lib := range versionData.Libraries {
		if strings.Contains(lib.Name, "natives") {
			libPath := filepath.Join(gameDir, "libraries", lib.Downloads.Artifact.Path)
			extractJar(libPath, nativesDir)
		}
	}

	// 构建类路径
	classpath := buildClasspath(gameDir, versionData.Libraries, versionPath)

	// 启动 Minecraft
	cmd := exec.Command("java",
		"-Djava.library.path="+nativesDir,
		"-cp", classpath,
		versionData.MainClass,
		"--username", "Enaium",
		"--version", version,
		"--gameDir", gameDir,
		"--assetsDir", filepath.Join(gameDir, "assets"),
		"--assetIndex", versionData.AssetIndex.ID,
		"--accessToken", "0",
		"--versionType", "RMCL 0.1.0",
	)
	cmd.Dir = gameDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		fmt.Printf("Failed to launch Minecraft: %v\n", err)
	}
}

func getManifest() VersionManifest {
	resp, err := http.Get("https://launchermeta.mojang.com/mc/game/version_manifest.json")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var manifest VersionManifest
	if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil {
		panic(err)
	}
	return manifest
}


func downloadVersionFiles(version VersionInfo, gameDir string) error {
	versionDir := filepath.Join(gameDir, "versions", version.ID)
	if err := os.MkdirAll(versionDir, 0755); err != nil {
		return err
	}

	resp, err := http.Get(version.URL)
	if err != nil {
		return fmt.Errorf("failed to download version manifest: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to download version manifest: HTTP status %d", resp.StatusCode)
	}

	versionFilePath := filepath.Join(versionDir, fmt.Sprintf("%s.json", version.ID))
	versionFile, err := os.Create(versionFilePath)
	if err != nil {
		return err
	}
	defer versionFile.Close()

	_, err = io.Copy(versionFile, resp.Body)
	if err != nil {
		return err
	}

	versionData := loadVersionConfig(versionFilePath)
	for _, lib := range versionData.Libraries {
		libPath := filepath.Join(gameDir, "libraries", lib.Downloads.Artifact.Path)
		dir := filepath.Dir(libPath)
		if err := os.MkdirAll(dir, 0755); err != nil {
			return err
		}

		if lib.Downloads.Artifact.URL == "" {
			return fmt.Errorf("missing URL for library %s", lib.Name)
		}

		resp, err := http.Get(lib.Downloads.Artifact.URL)
		if err != nil {
			return fmt.Errorf("failed to download library %s: %v", lib.Name, err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			return fmt.Errorf("failed to download library %s: HTTP status %d", lib.Name, resp.StatusCode)
		}

		totalSize := resp.ContentLength
		bar := progressbar.DefaultBytes(
			totalSize,
			"downloading "+lib.Name,
		)
		libFile, err := os.Create(libPath)
		if err != nil {
			return err
		}
		defer libFile.Close()

		_, err = io.Copy(io.MultiWriter(libFile, bar), resp.Body)
		if err != nil {
			return err
		}
		bar.Finish()
	}

	return nil
}


func buildClasspath(gameDir string, libraries []Library, versionPath string) string {
	var classpath []string
	for _, lib := range libraries {
		classpath = append(classpath, filepath.Join(gameDir, "libraries", lib.Downloads.Artifact.Path))
	}
	classpath = append(classpath, versionPath)
	return strings.Join(classpath, ":")
}

func loadVersionConfig(path string) Version {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	var version Version
	if err := json.NewDecoder(file).Decode(&version); err != nil {
		panic(err)
	}
	return version
}


func extractJar(jarPath, destDir string) {
	reader, err := zip.OpenReader(jarPath)
	if err != nil {
		panic(err)
	}
	defer reader.Close()

	for _, file := range reader.File {
		if !strings.HasPrefix(file.Name, "META-INF/") {
			filePath := filepath.Join(destDir, filepath.Base(file.Name))
			if _, err := os.Stat(filePath); err == nil {
				os.Remove(filePath)
			}

			fileReader, err := file.Open()
			if err != nil {
				panic(err)
			}
			defer fileReader.Close()

			destFile, err := os.Create(filePath)
			if err != nil {
				panic(err)
			}
			defer destFile.Close()

			_, err = io.Copy(destFile, fileReader)
			if err != nil {
				panic(err)
			}
		}
	}
}

